﻿/*
Copyright (c) 2012 Maksim Gordeev

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/

/*
 * Сделано в SharpDevelop.
 * Пользователь: max
 * Дата: 05.05.2012
 * Время: 17:35
 * 
 * Для изменения этого шаблона используйте Сервис | Настройка | Кодирование | Правка стандартных заголовков.
 */
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;

namespace jellybird.core
{
	/// <summary>
	/// Misc static methods
	/// </summary>
	public static class Helper
    {
        #region Serialization helper
        public static void SerializeJson(object source, TextWriter target)
        {
            JsonSerializer serializer = new JsonSerializer();
            serializer.Serialize(target, source);
        }

        public static void DeserializeJson<T>(TextReader source, out T result)
        {
            JsonSerializer serializer = new JsonSerializer();
            result = serializer.Deserialize<T>(new JsonTextReader(source));
        }
        #endregion

        #region DateTime related parsers etc
        public static readonly DateTime UnixStartDate=new DateTime(1970,1,1);
		
		public static int ToUnixTimestamp(DateTime dt)
		{
			return (int)(dt-UnixStartDate).TotalSeconds;
		}
		
		public static int UnixTimestamp()
		{
			return ToUnixTimestamp(DateTime.UtcNow);
		}
		
		/// <summary>
		/// Convert datetime struct to string in create_at field
		/// </summary>
		/// <param name="dt"></param>
		/// <returns>string like "Mon Nov 29 21:18:15 +0000 2010"</returns>
		public static string TwTimeFromDatetime(DateTime dt)
		{
			//получаем строку такого вида
			// "Mon Nov 29 21:18:15 +0000 2010"
			// http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx
			
			string tz_part="+0000";
			if(dt.Kind==DateTimeKind.Local)
			{
				TimeSpan offset=TimeZone.CurrentTimeZone.GetUtcOffset(dt);
				string sign="+";
				if(offset<TimeSpan.Zero)
				{
					sign="-";
					offset=offset.Duration();
				}
				tz_part=string.Format
					("{0}{1:00}{2:00}",
					 sign,
					 offset.Hours,
					 offset.Minutes);
			}
			
			string ret=dt.ToString
				("ddd MMM dd HH:mm:ss '"+tz_part+"' yyyy",
				 System.Globalization.CultureInfo.InvariantCulture);
			
			return ret;
		}
		
		/// <summary>
		/// string create_at from TwUser to DateTime
		/// </summary>
		/// <param name="time_string">time string "Mon Nov 29 21:18:15 +0000 2010"</param>
		/// <param name="result"></param>
		/// <returns>success or not</returns>
		public static bool TryParseFromTw(string time_string, ref DateTime result)
		{
			// "Mon Nov 29 21:18:15 +0000 2010"
			// "ddd MMM dd HH:mm:ss zzz yyyy"
			//примерно так
			//но непонятно с зоной и лидирующими нулями
			// http://www.w3.org/Protocols/rfc822/#z28
			// http://www.hackcraft.net/web/datetime/
			
			try
			{
				if(string.IsNullOrEmpty(time_string)) return false;
				
				//разобьём на части
				string[] parts=time_string.Split(' ');
				
				if(parts.Length!=6) return false;
				
				// 0 - день недели.
				// 1 - месяц
				int month_num=0;
				if(!TryParseMonth(parts[1],ref month_num)) return false;
				
				// 2 - число
				int date_num=0;
				if(!int.TryParse(parts[2],out date_num)) return false;
				
				// 3 - часы:мин:сек
				string[] time_parts=parts[3].Split(':');
				if(time_parts.Length!=3) return false;
				int hour=0;
				if(!int.TryParse(time_parts[0],out hour)) return false;
				int min=0;
				if(!int.TryParse(time_parts[1],out min)) return false;
				int sec=0;
				if(!int.TryParse(time_parts[2],out sec)) return false;
				
				// 4 зона (скорее всего всегда будет 0)
				//но на всякий случай нужно учесть
				//цифры 1,2 - часы
				//цифры 3,4 - минуты
				//символ 0 - '+' или '-'
				int hour_delta=0;
				if(!int.TryParse(parts[4].Substring(1,2),out hour_delta)) return false;
				int min_delta=0;
				if(!int.TryParse(parts[4].Substring(3,2),out min_delta)) return false;
				if(parts[4].StartsWith("+",StringComparison.Ordinal))
				{
					hour_delta=-hour_delta; //потому что чтобы получить GMT нужно будет отнять
					min_delta=-min_delta;
				}
				else if(!parts[4].StartsWith("-",StringComparison.Ordinal))
					return false;
				
				// 5 - год
				int year=0;
				if(!int.TryParse(parts[5],out year)) return false;
				
				//собираем дату
				DateTime dt_unspec=new DateTime
					(year,
					 month_num,
					 date_num,
					 hour,
					 min,
					 sec,
					 DateTimeKind.Unspecified);
				//объединияем с зоною если она есть
				dt_unspec=dt_unspec.AddHours(hour_delta);
				dt_unspec=dt_unspec.AddMinutes(min_delta);
				DateTime dt_utc=DateTime.SpecifyKind(dt_unspec, DateTimeKind.Utc);
				
				//и в возврат записываем местное время
				result=dt_utc.ToLocalTime();
				return true;
			}
			catch(Exception)
			{
				return false;
			}
		}
		
		/// <summary>
		/// Month string to month number
		/// </summary>
		/// <param name="month">Jan, Feb etc</param>
		/// <param name="month_number"></param>
		/// <returns>true if success</returns>
		public static bool TryParseMonth(string month, ref int month_number)
		{
			if(string.IsNullOrEmpty(month)) return false;
			
			for(int i=0;i<Constants.Months.Length;i++)
			{
				if(Constants.Months[i].StartsWith(month,StringComparison.OrdinalIgnoreCase))
				{
					month_number=i+1;
					return true;
				}
			}
			
			return false;
		}
		
		public static Color Tw2Color(string json_color)
		{
			int color_int=Convert.ToInt32(json_color,16);
			//add alpha=0xff
			Color ret=Color.FromArgb(0xFF,Color.FromArgb(color_int));
			return ret;
		}
		
		public static string Color2Tw(Color color)
		{
			//clear alpha part
			return string.Format("{0:X6}",Color.FromArgb(0x00,color).ToArgb());
		}
		#endregion
		
		#region url parsers
		
		public static string UrlEncode(IEnumerable<KeyValuePair<string,string>> pairs)
		{
			if(pairs==null) return string.Empty;
			
			int count=0;
			System.Text.StringBuilder sb=new System.Text.StringBuilder();
			foreach(KeyValuePair<string,string> pair in pairs)
			{
				if(count!=0) sb.Append(Constants.URLENCODE_PARAM_DELIMITER);
				sb.Append(Uri.EscapeDataString(pair.Key));
				sb.Append(Constants.URLENCODE_KEYVALUE_DELIMITER);
				sb.Append(Uri.EscapeDataString(pair.Value));
				count++;
			}
			
			return sb.ToString();
		}
		
		/// <summary>
		/// as described in
		///	http://www.w3.org/TR/1998/REC-html40-19980424/interact/forms.html#h-17.13.4
		/// </summary>
		/// <param name="inp"></param>
		/// <returns></returns>
		public static IEnumerable<KeyValuePair<string,string>> EnumNameValueFromUrlEncode(string inp)
		{
			if (string.IsNullOrEmpty(inp)) yield break;
			
			string[] parts=inp.Split(Constants.URLENCODE_PARAM_DELIMITER);
			int delim_pos=-1;
			foreach(string part in parts)
			{
				if(string.IsNullOrEmpty(part))
					continue;
				
				delim_pos=part.IndexOf(Constants.URLENCODE_KEYVALUE_DELIMITER);
				if (delim_pos==-1)
				{
					yield return new KeyValuePair<string,string>(part,string.Empty);
					continue;
				}
				if (delim_pos==0)
				{
					continue;
				}
				if(delim_pos==(part.Length-1))
				{
					yield return new KeyValuePair<string,string>(part.Substring(0,part.Length-1),string.Empty);
					continue;
				}
				yield return new KeyValuePair<string,string>(part.Substring(0,delim_pos),part.Substring(delim_pos+1));
			}
		}
		
		/// <summary>
		/// С заменой + на пробел
		/// http://www.w3.org/TR/1998/REC-html40-19980424/interact/forms.html#h-17.13.4
		/// </summary>
		/// <param name="inp"></param>
		/// <returns></returns>
		public static string DecodeUrlEncoded(string inp)
		{
			string ret=inp.Replace('+',' ');
			ret=Uri.UnescapeDataString(ret);
			return ret;
		}
		
		public static string BuildBaseUrl(string group, string name)
		{
			return BuildBaseUrl(Constants.API_TWITTER_COM,Constants.API_VERSION_1,group,name);
		}
		
		public static string BuildBaseUrl(string api_base,string version,string group, string name)
		{
			if(string.IsNullOrEmpty(api_base)) throw new ArgumentException("api_base cannot be empty");
			System.Text.StringBuilder sb=new System.Text.StringBuilder();
			sb.Append(api_base);
			if(!string.IsNullOrEmpty(version))
			{
				sb.Append('/');
				sb.Append(version);
			}
			if(!string.IsNullOrEmpty(group))
			{
				sb.Append('/');
				sb.Append(group);
			}
			if(!string.IsNullOrEmpty(name))
			{
				sb.Append('/');
				sb.Append(name);
			}
			return sb.ToString();
		}
		#endregion
		
		#region Simple download
		/// <summary>
		/// Sync method.
		/// </summary>
		/// <param name="url"></param>
		/// <returns>memory stream - copy of WebResponse.GetResponseStream</returns>
		public static Stream DownloadSmallStream(string url)
		{
			WebRequest web_req=WebRequest.Create(url);
			WebResponse web_resp=null;
			MemoryStream res_stream=new MemoryStream();
			int buffer_len=512;
			try
			{
				web_resp=web_req.GetResponse();
				Stream resp_stream=web_resp.GetResponseStream();
				if(resp_stream!=null)
				{
					byte[] buffer=new byte[buffer_len];
					int bytes_readed=0;
					while((bytes_readed=resp_stream.Read(buffer,0,buffer_len))!=0)
					{
						res_stream.Write(buffer,0,bytes_readed);
					}
				}
			}
			catch(Exception ex)
			{
				throw ex;
			}
			finally
			{
				if(web_resp!=null)
					web_resp.Close();
			}
			return res_stream;
		}
		#endregion

		#region Useful for debug
		public static string HttpWebResponse2String(HttpWebResponse arg)
		{
			StringBuilder ret_b=new StringBuilder();
			
			
			
			ret_b.AppendLine();
			
			if(arg!=null)
			{
				ret_b.AppendFormat("Server: {0}{1}{0}Headers:",Environment.NewLine,arg.ResponseUri);
				foreach(string key in arg.Headers.Keys)
				{
					ret_b.AppendFormat("{2}{0}: {1}",key,arg.Headers[key],Environment.NewLine);
				}
			}
			else
			{
				ret_b.Append("Response is null");
				ret_b.AppendLine();
			}
			
			return ret_b.ToString();
		}
		
		
		
		public static string HttpWebRequest2String(HttpWebRequest arg)
		{
			StringBuilder ret_b=new StringBuilder();
			
			ret_b.AppendLine();
			
			if(arg!=null)
			{
				ret_b.AppendFormat("Address:{0}{1}{0}Headers:",Environment.NewLine,arg.Address);
				foreach(string key in arg.Headers.Keys)
				{
					ret_b.AppendFormat("{2}{0}: {1}",key,arg.Headers[key],Environment.NewLine);
				}
			}
			else
			{
				ret_b.Append("Request is null");
			}
			
			return ret_b.ToString();
		}
		
		private const int char_block_len=256;
		
		public static event EventHandler<MessageEventArg> DebugMessage;
		
		internal static void OnDebugMessage(string mess)
		{
			if((mess!=null)&&(DebugMessage!=null))
			{
				DebugMessage.Invoke("Core",new MessageEventArg(mess));
			}
		}
		#endregion
		
		public static Size TwSize2Size(TwImageSize tw_size)
		{
			Size ret=new Size();
			switch (tw_size)
			{
				case TwImageSize.bigger:
					ret.Height=Constants.SIZE_BIGGER;
					ret.Width=Constants.SIZE_BIGGER;
					break;
				case TwImageSize.normal:
					ret.Height=Constants.SIZE_NORMAL;
					ret.Width=Constants.SIZE_NORMAL;
					break;
				case TwImageSize.mini:
					ret.Height=Constants.SIZE_MINI;
					ret.Width=Constants.SIZE_MINI;
					break;
			}
			return ret;
		}
		
		public static TwImageSize Size2TwSize(Size size)
		{
			switch(size.Height)
			{
				case Constants.SIZE_MINI:
					if(size.Width==Constants.SIZE_MINI)
						return TwImageSize.mini;
					break;
					
				case Constants.SIZE_NORMAL:
					if(size.Width==Constants.SIZE_NORMAL)
						return TwImageSize.normal;
					break;
					
				case Constants.SIZE_BIGGER:
					if(size.Width==Constants.SIZE_BIGGER)
						return TwImageSize.bigger;
					break;
			}
			
			return TwImageSize.original;
		}
	}
	
	public class MessageEventArg:EventArgs
	{
		public MessageEventArg(string message)
		{
			if (message == null)
				throw new ArgumentNullException("message");
			
			this.message = string.Format
				("[{0}] Thread: 0x{1:X} {2}",
				 DateTime.Now.ToLongTimeString(),
				 System.Threading.Thread.CurrentThread.ManagedThreadId,
				 message);
		}
		
		string message;
		
		public string Message {
			get { return message; }
			set { message = value; }
		}
	}
}
